Initial release | 2007 |
---|---|
Stable release | 2.2.0 SP2 / March 31, 2011 |
Preview release | 2.2.0 SP1 / January 17, 2011 |
Development status | Production |
Written in | Flex, Java, Groovy |
Platform | Platform independent |
License | LGPL 2 |
Website | www.graniteds.org |
Granite Data Services (GraniteDS or GDS) is a comprehensive development and integration solution for building Flex / JavaEE RIA applications. The entire framework is open-source and released under the LGPL v2 license.
It features:
Contents |
See integration & features stack image for a comprehensive overview of JPA engines, JavaEE frameworks and Java application servers supported by GraniteDS.
GraniteDS integrates with the major JavaEE frameworks:
Additionally, it comes with a basic Java component framework (called Pojo) that let you expose simple Java beans as remote destinations (with 3 possible scopes: request, session or application).
Accessing these frameworks from a Flex application is straightforward if you use:
With a basic EJB3 like this one (it would work the same with other frameworks):
@RemoteDestination public interface Hello { public String welcome(String name); } @Stateless @Local(Hello.class) public class HelloBean { public String welcome(String name) { return "Hello " + name + "!"; } }
You will get these ActionScript3 generated classes:
[RemoteClass(alias="path.to.java.Hello")] public class Hello extends HelloBase { // put your custom code here... } public class HelloBase extends Component { public function welcome(arg0:String, resultHandler:Object = null, faultHandler:Function = null):void { // skipped generated code (actually calls the remote method)... } }
And here is a sample remote call from an injected component:
[In] public var hello:Hello; hello.welcome("World", welcomeResult); private function welcomeResult(event:TideResultEvent):void { trace(event.result); // print "Hello World!" }
Note that the variable name "hello" is implicitly bound to the uncapitalized name of the EJB3 interface (if your EJB3 was named "MyService", you would use a variable named "myService").
The @RemoteDestination on the interface serves two purposes:
A type-safe annotation for dependency injection (Inject) is also available for the Spring and CDI frameworks.
GraniteDS integrates with the major Java Persistence API engines available in the JavaEE world:
This integration is again closely coupled with code generation tools available in GraniteDS that replicate on-the-fly JPA entities into an ActionScript3 model. The serialization of JPA entities from Java to Flex (or from Flex to Java) uses a custom mechanism called externalization, that preserves the entire state of the objects, instead of keeping only public properties.
With code generation and externalization, GraniteDS is able to retain JPA entities metadata in the ActionScript3 entities, such as their entire initialization states. Serializing partially uninitialized entities with GraniteDS guaranties that:
Thus, this mechanism prevents database and memory overloads (only expected references between entities are actually loaded end serialized) and unexpected database updates (an uninitialized property sent back from Flex to Java will not be considered as a null reference or an empty collection).
Together with the Tide client framework, you may even benefit from transparent initialization, when accessing a lazy-loaded property in your Flex code: this access will trigger an asynchronous initialization request to the server that will serialize back the initialized value.
With a simple data model like this one (note the LAZY fetch type of the addresses set):
@Entity public class Person { @Id @GeneratedValue private Integer id; @Version private Integer version; @Basic private String firstname; @Basic private String lastname; @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="person") private Set<Address> addresses; public Integer getId() { return id; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } public Set<Address> getAddresses() { return contacts; } public void setAddresses(Set<Address> addresses) { this.addresses = addresses; } } @Entity public class Address { @Id @GeneratedValue private Integer id; @ManyToOne(optional=false) private Person person; // skipped code... }
You will get these ActionScript3 generated classes:
[Bindable] [RemoteClass(alias="path.to.java.Person")] public class Person extends PersonBase { // put your custom code here... } public class PersonBase implements IExternalizable { private var __initialized:Boolean = true; private var __detachedState:String = null; private var _id:Number; private var _version:Number; private var _firstname:String; private var _lastname:String; private var _addresses:ListCollectionView; public function get id():Number { return _id; } public function get fisrtname():String { return _firstname; } public function set firstname(value:String):void { _firstname = value; } // other getters/setters... public override function readExternal(input:IDataInput):void { __initialized = input.readObject() as Boolean; __detachedState = input.readObject() as String; if (meta::isInitialized()) { _id = function(o:*):Number { return (o is Number ? o as Number : Number.NaN) } (input.readObject()); _addresses = input.readObject() as ListCollectionView; _firstname = input.readObject() as String; _lastname = input.readObject() as String; _version = function(o:*):Number { return (o is Number ? o as Number : Number.NaN) } (input.readObject()); } else { _id = function(o:*):Number { return (o is Number ? o as Number : Number.NaN) } (input.readObject()); } } public override function writeExternal(output:IDataOutput):void { // ... } }
The important things to notice here are:
Real-time messaging lets a Flex application publish to and receive from other clients data in so-called real-time. Messages may also be sent from the JavaEE server itself (known as data push), such as special alerts or progress events.
The implementation of real-time messaging in GraniteDS is based on the Comet model, now standardized in the Servlet 3.0 specification (JSR-303). Specifically, the transport protocol relies on a partial implementation of the standard Bayeux protocol.
On the server side, this implementation relies on specific servlet containers capabilities (such as Tomcat's CometProcessor or Jetty's Continuations) or on standardized Servlet 3.0 implementations when available. These implementations allow scalable real-time messaging by providing a way of releasing servlet threads when the HTTP connections are idle: the standard thread-per-connection model is replaced by a much more efficient thread-by-request model (see explanations in Jetty documentation).
GraniteDS uses a long polling model for managing HTTP requests between Flex client applications and the server: when a request for incoming data stays idle for a given amount of time (usually 30 s), it is closed and immediately reopened. This model prevents unexpected closing of idle HTTP connections traversing Internet proxies.
Real-time messaging with GraniteDS is based on a consumer–producer model and lets you define topics and subtopics for partitioning groups of messages. You may also filter topic's messages by using specific selectors.
Implementing a basic chat application with GraniteDS requires configuring a specific servlet in the web.xml file of your application:
<servlet> <servlet-name>GravityServlet</servlet-name> <servlet-class>org.granite.gravity.servlet3.GravityAsyncServlet</servlet-class> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>GravityServlet</servlet-name> <url-pattern>/gravity/*</url-pattern> </servlet-mapping>
The server configuration requires also the definition of a specific destination (services-config.xml):
<services-config> <services> <service id="messaging-service" class="flex.messaging.services.MessagingService" messageTypes="flex.messaging.messages.AsyncMessage"> <adapters> <adapter-definition id="default" class="org.granite.gravity.adapters.SimpleServiceAdapter" default="true"/> </adapters> <destination id="gravity"> <channels> <channel ref="my-gravityamf"/> </channels> </destination> </service> </services> <channels> <channel-definition id="my-gravityamf" class="org.granite.gravity.channels.GravityChannel"> <endpoint uri="http://{server.name}:{server.port}/{context.root}/gravity/amf" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition> </channels> </services-config>
In a Flex application, you may then use the Consumer and Producer classes in order communicate with other clients:
private function connect(nickname:String):void { consumer = new Consumer(); consumer.destination = "gravity"; consumer.topic = "discussion"; consumer.subscribe(); consumer.addEventListener(MessageEvent.MESSAGE, messageHandler); producer = new Producer(); producer.destination = "gravity"; producer.topic = "discussion"; } private function messageHandler(event:MessageEvent):void { var msg:AsyncMessage = event.message as AsyncMessage; var message:String = msg.body as String; trace('You have received a message: ' + message); } private function send(message:String):void { var msg:AsyncMessage = new AsyncMessage(); msg.body = message; producer.send(msg); }
In this quick code snippet, all Flex applications subscribe and publish to the same topic ("discussion"), so each message sent by a client is dispatched to other clients.
Together with the Tide framework, one may quickly configure a JavaEE server to automatically dispatch an entity's modifications in a specific topic: updates of shared data are then instantly visible in all clients (see GraniteDS documentation for more details).
TODO
TODO
|